﻿/*   
    Copyright (C) 2009 Galaktika Corporation ZAO

    This file is a part of Galaktika.BusinessMonitor
 
    Galaktika.BusinessMonitor is a free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
      
    You should have received a copy of the GNU General Public License
    along with Galaktika.BusinessMonitor.  If not, see <http://www.gnu.org/licenses/>.
  
    If GPL v.3 is not suitable for your products or company,
    Galaktika Corp provides Galaktika.BusinessMonitor under a flexible commercial license
    designed to meet your specific usage and distribution requirements.
    If you have already obtained a commercial license from Galaktika Corp,
    you can use this file under those license terms.
*/

using System;
using System.Collections.Generic;
using System.Windows;

namespace Galaktika.BI.Silverlight.ClientApp
{
    public class GridDescriptor
    {
        #region LayoutItemDictionary
        private class LayoutItemDictionary
        {
            #region LocationKey
            private class LocationKey
            {
                public LocationKey(Point point)
                    : this(point.X, point.Y)
                {
                }

                public LocationKey(double x, double y)
                {
                    this.X = x;
                    this.Y = y;
                }

                public readonly double X;
                public readonly double Y;

                public override bool Equals(object obj)
                {
                    LocationKey key = obj as LocationKey;
                    if (key != null)
                    {
                        return key.X == this.X && key.Y == Y;
                    }

                    return base.Equals(obj);
                }

                public override int GetHashCode()
                {
                    int hash1 = this.X.GetHashCode();
                    int hash2 = this.Y.GetHashCode();
                    return (hash1 << 2) & hash2;
                }

                public override string ToString()
                {
                    return string.Format("X={0} Y={1}", this.X, this.Y);
                }
            }
            #endregion

            private Dictionary<LocationKey, LayoutItemBase> m_Locations =
                new Dictionary<LocationKey, LayoutItemBase>();
            public LayoutItemDictionary()
            {
            }

            public bool IsLeaf
            {
                get
                {
                    return m_Locations.Count == 1;
                }
            }

            public bool IsGroup
            {
                get
                {
                    if (this.IsLeaf)
                    {
                        LayoutItemBase item = this.GetLeafElement();
                        return item.TypeName == LayoutItemTypeName.LayoutGroup;
                    }

                    return false;
                }
            }

            public LayoutItemBase GetLeafElement()
            {
                foreach (LayoutItemBase item in m_Locations.Values)
                {
                    return item;
                }

                return null;
            }

            //private Dictionary<string, LayoutItemBase> m_Groups = new Dictionary<string, LayoutItemBase>();
            //public IDictionary<string, LayoutItemBase> Groups
            //{
            //    get
            //    {
            //        return m_Groups;
            //    }
            //}

            public void Add(LayoutItemBase item)
            {
                //switch (item.TypeName)
                //{
                //    case LayoutItemTypeName.LayoutControlItem:
                //    case LayoutItemTypeName.EmptySpaceItem:
                //        break;
                //    case LayoutItemTypeName.LayoutGroup:
                //        break;
                //    default:
                //        break;
                //}

                GetOrCreateItems(m_Columns, item.Location.X).Add(item);
                GetOrCreateItems(m_Rows, item.Location.Y).Add(item);
                m_Locations[new LocationKey(item.Location)] = item;

                m_LeftMost = Math.Min(m_LeftMost, item.Bounds.Left);
                m_TopMost = Math.Min(m_TopMost, item.Bounds.Top);
                m_RightMost = Math.Max(m_RightMost, item.Bounds.Right);
                m_BottomMost = Math.Max(m_BottomMost, item.Bounds.Bottom);
            }

            public void AddRange(IEnumerable<LayoutItemBase> items)
            {
                foreach (LayoutItemBase item in items)
                {
                    this.Add(item);
                }
            }

            public int Count
            {
                get
                {
                    return m_Locations.Count;
                }
            }

            private Dictionary<double, List<LayoutItemBase>> m_Columns =
                new Dictionary<double, List<LayoutItemBase>>();
            public IDictionary<double, List<LayoutItemBase>> Columns
            {
                get
                {
                    return m_Columns;
                }
            }

            private Dictionary<double, List<LayoutItemBase>> m_Rows =
                new Dictionary<double, List<LayoutItemBase>>();
            public IDictionary<double, List<LayoutItemBase>> Rows
            {
                get
                {
                    return m_Rows;
                }
            }

            private double m_LeftMost = double.MaxValue;
            public double LeftMost
            {
                get
                {
                    return m_LeftMost;
                }
            }

            private double m_TopMost = double.MaxValue;
            public double TopMost
            {
                get
                {
                    return m_TopMost;
                }
            }

            private double m_BottomMost = double.MinValue;
            public double BottomMost
            {
                get 
                { 
                    return m_BottomMost; 
                }
            }

            private double m_RightMost = double.MinValue;
            public double RightMost
            {
                get 
                { 
                    return m_RightMost; 
                }
            }

            public LayoutItemBase this[double x, double y]
            {
                get
                {
                    LayoutItemBase item;
                    m_Locations.TryGetValue(new LocationKey(x, y), out item);
                    return item;
                }
            }

            public LayoutItemBase this[Point location]
            {
                get
                {
                    LayoutItemBase item;
                    m_Locations.TryGetValue(new LocationKey(location), out item);
                    return item;
                }
            }

            public IEnumerable<LayoutItemBase> AllItems
            {
                get
                {
                    return m_Locations.Values;
                }
            }

            public void SortAll()
            {
                this.SortList(m_Rows.Values,
                    (x, y) => Comparer<double>.Default.Compare(x.Location.X, y.Location.X));
                this.SortList(m_Columns.Values,
                    (x, y) => Comparer<double>.Default.Compare(x.Location.Y, y.Location.Y));
            }

            public IEnumerable<double> GetSortedColumnKeys()
            {
                return this.GetSortedKeys(m_Columns.Keys);
            }

            public IEnumerable<double> GetSortedRowKeys()
            {
                return this.GetSortedKeys(m_Rows.Keys);
            }

            private IEnumerable<double> GetSortedKeys(Dictionary<double, List<LayoutItemBase>>.KeyCollection keys)
            {
                double[] keyArray = new double[keys.Count];
                keys.CopyTo(keyArray, 0);
                Array.Sort<double>(keyArray, (x, y) => Comparer<double>.Default.Compare(x, y));

                return keyArray;
            }

            private void SortList(IEnumerable<List<LayoutItemBase>> lists, Comparison<LayoutItemBase> comparision)
            {
                foreach (List<LayoutItemBase> list in lists)
                {
                    list.Sort(comparision);
                }
            }

            private static List<LayoutItemBase> GetOrCreateItems(Dictionary<double, List<LayoutItemBase>> source, double key)
            {
                List<LayoutItemBase> items;
                source.TryGetValue(key, out items);
                if (items == null)
                {
                    items = new List<LayoutItemBase>();
                    source.Add(key, items);
                }

                return items;
            }

            private static void RemoveAndDestroyIfLast(Dictionary<double, List<LayoutItemBase>> source, double key, LayoutItemBase item)
            {
                List<LayoutItemBase> items;
                source.TryGetValue(key, out items);
                if (items != null)
                {
                    //items = new List<LayoutItemBase>();
                    items.Remove(item);
                    if (items.Count == 0)
                    {
                        source.Remove(key);
                    }
                }
            }
        }
        #endregion
        #region LayoutGroupContainer
        private class LayoutGroupContainer
        {
            public LayoutGroupContainer(LayoutItemBase root)
                : this(root, new LayoutItemDictionary(), new Dictionary<string, LayoutGroupContainer>())
            {
            }

            public LayoutGroupContainer(
                LayoutItemBase root, 
                LayoutItemDictionary items, 
                IDictionary<string, LayoutGroupContainer> groups)
            {
                this.Root = root;
                this.Items = items;
                this.Groups = groups;
            }

            /// <summary>
            /// Корневая группа.
            /// </summary>
            public readonly LayoutItemBase Root;
            /// <summary>
            /// Коллекция элементов группы. Содержит опсание вложенных групп как обычных контролов.
            /// Элементы не принадлежащие к корневой группе в коллекцию не попадают.
            /// </summary>
            public readonly LayoutItemDictionary Items;
            /// <summary>
            /// Дочерние группы.
            /// </summary>
            public readonly IDictionary<string, LayoutGroupContainer> Groups;
        }
        #endregion
        #region CellItem
        private class CellItem
        {
            public CellItem()
            {
                this.Items = new LayoutItemDictionary();
            }

            public readonly LayoutItemDictionary Items;
            public double Left;
            public double Top;
            public double Right;
            public double Bottom;
            public int RowIndex;
            public int ColumnIndex;

            private Rect m_Bounds = default(Rect);
            public Rect Bounds
            {
                get
                {
                    if (m_Bounds == default(Rect))
                    {
                        m_Bounds = new Rect(this.Left, this.Top, this.Right - this.Left, this.Bottom - this.Top);
                    }

                    return m_Bounds;
                }
            }

            public bool Contains(Rect rect)
            {
                return this.Bounds.Contains(new Point(rect.X, rect.Y)) && this.Bounds.Contains(new Point(rect.Right, rect.Bottom));
            }
        }
        #endregion

        public int ColumnCount { get; private set; }
        public int RowCount { get; private set; }
        public IList<GridCellDescriptor> Children { get; private set; }
        public LayoutItemBase LayoutItem { get; private set; }

        public bool IsLeaf { get; private set; }
        public bool IsGroup { get; private set; }

        //private GridDescriptor(LayoutItemDictionary dictionary)
        private GridDescriptor(LayoutGroupContainer container)
        {
            this.Children = new List<GridCellDescriptor>();
            LayoutItemDictionary dictionary = container.Items;
            //this.IsGroup = dictionary.IsGroup;
            this.IsLeaf = dictionary.IsLeaf;
            //if (this.IsGroup)
            //{
            //    this.LayoutItem = dictionary.GetLeafElement();
            //}
            if (this.IsLeaf)
            {
                this.LayoutItem = dictionary.GetLeafElement();
                return;
            }

            dictionary.SortAll();

            List<CellItem> cells = new List<CellItem>();
            CellItem cellItem = null; 
            int columnIndex = 0;

            foreach (double columnKey in dictionary.GetSortedColumnKeys())
            {
                List<LayoutItemBase> items = dictionary.Columns[columnKey];
                LayoutItemBase item = items[0];
                if (item.Bounds.Top == dictionary.TopMost &&
                    IsLineItem(item,
                    currentItem => dictionary[currentItem.Location.X, currentItem.Bounds.Bottom],
                    currentItem => currentItem.Bounds.Bottom == dictionary.BottomMost
                    ))
                {
                    if (cellItem != null)
                    {
                        cellItem.Right = item.Bounds.Left;
                        cellItem.Bottom = dictionary.BottomMost;
                    }
                    cellItem = new CellItem() { Left = item.Bounds.Left, Top = dictionary.TopMost, Right = dictionary.RightMost, Bottom = dictionary.BottomMost };
                    cellItem.RowIndex = 0;
                    cellItem.ColumnIndex = columnIndex++;
                    cells.Add(cellItem);
                }
            }

            int rowIndex = 0;
            List<CellItem> cellsUnder = null;// new List<CellItem>(cells);
            foreach (double rowKey in dictionary.GetSortedRowKeys())
            {
                List<LayoutItemBase> items = dictionary.Rows[rowKey];
                LayoutItemBase item = items[0];
                if (item.Bounds.Left == dictionary.LeftMost &&
                    IsLineItem(item,
                    currentItem => 
                        {
                            LayoutItemBase n = dictionary[currentItem.Bounds.Right, currentItem.Location.Y];
                            return n;
                        },
                    currentItem => currentItem.Bounds.Right == dictionary.RightMost))
                {
                    List<CellItem> newCellsUnder = new List<CellItem>();
                    if (cellsUnder != null)
                    {
                        foreach (CellItem cellUnder in cellsUnder)
                        {
                            cellUnder.Bottom = item.Bounds.Top;
                            CellItem newCellItem = new CellItem() { Left = cellUnder.Left, Top = cellUnder.Bottom, Right = cellUnder.Right, Bottom = dictionary.BottomMost };
                            newCellItem.ColumnIndex = cellUnder.ColumnIndex;
                            newCellItem.RowIndex = rowIndex;
                            newCellsUnder.Add(newCellItem);
                            cells.Add(newCellItem);
                        }
                    }
                    else
                    {
                        newCellsUnder.AddRange(cells);
                    }
                    rowIndex++;
                    cellsUnder = newCellsUnder;
                }
            }

            foreach (LayoutItemBase item in dictionary.AllItems)
            {
                foreach (CellItem cell in cells)
                {
                    if (cell.Contains(item.Bounds))
                    {
                        cell.Items.Add(item);
                        break;
                    }
                }
            }

            this.ColumnCount = columnIndex;
            this.RowCount = rowIndex;
            var grids = new List<GridCellDescriptor>();
            this.Children = grids;
            foreach (CellItem cell in cells)
            {
                if (cell.Items.IsGroup)
                {
                    LayoutItemBase groupItem = cell.Items.GetLeafElement();
                    grids.Add(new GridCellDescriptor(
                        cell.RowIndex,
                        cell.ColumnIndex,
                        cell.Bounds,
                        new GridDescriptor(
                            container.Groups[groupItem.LayoutItemName]) { IsGroup = true, LayoutItem = groupItem }));
                }
                else
                {
                    grids.Add(new GridCellDescriptor(
                        cell.RowIndex,
                        cell.ColumnIndex,
                        cell.Bounds,
                        new GridDescriptor(
                            new LayoutGroupContainer(container.Root, cell.Items, container.Groups))));
                }
            }
        }

        public static GridDescriptor Create(LayoutContainer layout, bool trimBottom)
        {
            LayoutGroupContainer root = null;
            foreach (LayoutItemBase item in layout.Items)
            {
                if (item.ParentName == KnownLayoutItemName.Customization) continue;
                switch (item.TypeName)
                {
                    case LayoutItemTypeName.EmptySpaceItem:
                        //if (trimBottom && (root.Root.Bounds.Bottom - item.Bounds.Bottom < 3.0))
                        //{
                        //    root.Root.Size = new Size(root.Root.Size.Width, item.Location.Y);
                        //}
                        //else
                        //{
                        //    if (!string.IsNullOrEmpty(item.ParentName))
                        //    {
                        //        LayoutGroupContainer parent;
                        //        root.Groups.TryGetValue(item.ParentName, out parent);
                        //        if (parent != null)
                        //        {
                        //            parent.Items.Add(item);
                        //        }
                        //    }
                        //}
                        //break;
                    case LayoutItemTypeName.LayoutControlItem:
                    case LayoutItemTypeName.SimpleSeparator:
                    case LayoutItemTypeName.SimpleLabelItem:
                        if (!string.IsNullOrEmpty(item.ParentName))
                        {
                            LayoutGroupContainer parent;
                            root.Groups.TryGetValue(item.ParentName, out parent);
                            if (parent != null)
                            {
                                parent.Items.Add(item);
                            }
                        }
                        break;
                    case LayoutItemTypeName.LayoutGroup:
                        if (root == null)
                        {
                            root = new LayoutGroupContainer(item);
                            root.Groups.Add(root.Root.LayoutItemName, root);
                        }
                        else
                        {
                            root.Groups.Add(item.LayoutItemName, new LayoutGroupContainer(item));
                        }
                        if (!string.IsNullOrEmpty(item.ParentName))
                        {
                            LayoutGroupContainer parent;
                            root.Groups.TryGetValue(item.ParentName, out parent);
                            if (parent != null)
                            {
                                parent.Items.Add(item);
                            }
                        }
                        break;
                    default:
                        break;
                }
            }

            GridDescriptor descriptor = new GridDescriptor(root);
            return descriptor;
            /*
            LayoutItemDictionary dictionary = new LayoutItemDictionary();
            foreach (LayoutItemBase item in layout.Items)
            {
                dictionary.Add(item);
            }

            GridDescriptor descriptor = new GridDescriptor(dictionary);
            return descriptor;
            */
        }

        private delegate LayoutItemBase GetNextItem(LayoutItemBase currentItem);
        private delegate bool IsFinalItem(LayoutItemBase currentItem);
        private static bool IsLineItem(LayoutItemBase item, GetNextItem getNextItem, IsFinalItem isFinalItem)
        {
            if (isFinalItem(item))
            {
                return true;
            }

            LayoutItemBase nextItem = getNextItem(item);
            if (nextItem == null)
            {
                return false;
            }

            return IsLineItem(nextItem, getNextItem, isFinalItem);
        }
    }
}
